﻿#region Librerias

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.IO.Compression;
using System.Net;
using System.IO;
using ESF.General;
using Microsoft.Ajax.Utilities;

#endregion


namespace ESF.Web.Modules
{
	public sealed class CompressionModule : IHttpModule 
	{
		#region IHttpModule Members
	
		public void Dispose () {}
		
		public void Init (HttpApplication context) 
		{
			context.PreRequestHandlerExecute += context_PreRequestHandlerExecute;
			context.BeginRequest += context_BeginRequest;
			context.EndRequest += context_EndRequest;
		}
 
		#endregion IHttpModule Members


		#region Request Methods

		private void context_PreRequestHandlerExecute(object sender, EventArgs e)
		{
			var app = (HttpApplication)sender;


			if (ShouldCompressPage(app.Context))
			{
				ModuleHandlerHelper.AddZipFilter (app.Context);
			}
		}


		private void context_BeginRequest(object sender, EventArgs e)
		{
			HttpApplication app = (HttpApplication)sender;


			if (//app.Request.ServerVariables["SERVER_SOFTWARE"].StartsWith ("Microsoft-IIS/6.0") 
				app.Request.Path.Contains("WebResource.axd"))
			{
				SetCachingHeaders(app.Context);

				if (// Si c = null es el request original
					app.Context.Request.QueryString["c"] == null 
					&& (ModuleHandlerHelper.IsEncodingAccepted(app.Context, ModuleHandlerHelper.DEFLATE) 
						|| ModuleHandlerHelper.IsEncodingAccepted(app.Context, ModuleHandlerHelper.GZIP))
				)
				{
					app.CompleteRequest();
				}
			}
		}


		private void context_EndRequest(object sender, EventArgs e)
		{
			HttpApplication app = (HttpApplication)sender;

			byte [] contents;

			string contentEncoding;
			string contentType;


			if (!ModuleHandlerHelper.ShouldCompressPage(app.Context))
			{
				return;
			}

			if (app.Request.Path.Contains("WebResource.axd") && app.Context.Request.QueryString["c"] == null)
			{
				contents = GetAddCompressedBytesToCache (app, out contentEncoding, out contentType);
				

				ModuleHandlerHelper.AddHeaderEncoding (app.Context, contentEncoding);

				app.Context.Response.ContentType = contentType;

				app.Context.Response.BinaryWrite(contents);
			}
		}

		#endregion
		
 
		#region Utility
		
		private static bool ShouldCompressPage (HttpContext pContext)
		{
			return //!pContext.Request.Path.Contains("WebResource.axd") 
				(pContext.CurrentHandler is System.Web.UI.Page) 
				&& ModuleHandlerHelper.ShouldCompressPage(pContext);
		}

		
		private static void SetCachingHeaders(HttpContext pContext)
		{
			string etag = "\"" + pContext.Request.QueryString.ToString().GetHashCode().ToString() + "\"";
			string incomingEtag = pContext.Request.Headers["If-None-Match"];


			pContext.Response.Cache.VaryByHeaders["Accept-Encoding"] = true;
			pContext.Response.Cache.SetExpires(DateTime.Now.AddDays(30));
			pContext.Response.Cache.SetCacheability(HttpCacheability.Public);
			pContext.Response.Cache.SetLastModified(DateTime.Now.AddDays(-30));
			pContext.Response.Cache.SetETag(etag);

			ModuleHandlerHelper.AddESFDescriptionHeader (pContext, "ZWebResource");

			if (String.Compare(incomingEtag, etag) == 0)
			{
				pContext.Response.StatusCode = (int)HttpStatusCode.NotModified;
				pContext.Response.End();
			}
		}

 
		/// <summary>
		/// Adds a compressed byte array into the application items.
		/// <remarks>
		/// This is done for performance reasons so it doesn't have to
		/// create an HTTP request every time it serves the WebResource.axd.
		/// </remarks>
		/// </summary>
		private static byte [] GetAddCompressedBytesToCache (HttpApplication app, out string pContentEncoding, out string pContentType) 
		{
			byte[] buffer;

			HttpWebRequest request;

			string key;


			key = app.Context.Request.Url.OriginalString;


			if (app.Application[key] != null)
			{
				pContentEncoding = app.Application[key + "_ContentEncoding"] as string;
				pContentType = app.Application[key + "_ContentType"] as string;
				return (byte []) app.Application[key];
			}


			request = (HttpWebRequest)WebRequest.Create(app.Context.Request.Url.OriginalString + "&c=1");
			request.KeepAlive = false;
			request.Method = "GET";
			request.CookieContainer = new CookieContainer ();


			foreach (string cookieName in app.Context.Request.Cookies.AllKeys)
			{
				if (!string.IsNullOrWhiteSpace (app.Context.Request.Cookies[cookieName].Value))
				{
					request.CookieContainer.Add (
						new Cookie(
							cookieName, 
							app.Context.Request.Cookies[cookieName].Value, 
							app.Context.Request.Cookies[cookieName].Path, 
							request.RequestUri.Host));
				}
			}

			using (HttpWebResponse response = request.GetResponse() as HttpWebResponse) 
			{
				Stream resStream;

				MemoryStream dataStream;


				if (response == null) 
				{
					throw new GenericException (string.Format ("The Request {0} has no results", key));
				}

				pContentEncoding = response.ContentEncoding;
				pContentType = response.ContentType;
				
				#region Al Buffer

				resStream = response.GetResponseStream ();

				dataStream = new MemoryStream();

				resStream.CopyToStream (dataStream);

				resStream.Dispose();
 
				buffer = dataStream.ToArray();
			
				dataStream.Dispose();

				#endregion


				switch (response.ContentType)
				{
					case "application/x-javascript":
					case "text/javascript":
						buffer = UTF8Encoding.UTF8.GetBytes (new Minifier().MinifyJavaScript (UTF8Encoding.UTF8.GetString (buffer)));
					break;


					case "text/css":
						buffer = UTF8Encoding.UTF8.GetBytes (new Minifier ().MinifyStyleSheet (UTF8Encoding.UTF8.GetString (buffer)));
					break;
				}


				buffer = CompressResponse (buffer, app, ref pContentEncoding);

				app.Application.Add(key, buffer);
				app.Application.Add(key + "_ContentEncoding", pContentEncoding);
				app.Application.Add(key + "_ContentType", pContentType);
			}

			return buffer;
		}


		/// <summary>
		/// Compresses the response stream if the browser allows it.
		/// </summary>
		/// <remarks>This is only done for WebResource requests, as it requires
		/// the entire response to be available. Don't use for normal requests as
		/// it will up the memory usage and screw up the IIS pipeline.</remarks>
		private static byte [] CompressResponse (byte [] pBuffer, HttpApplication app, ref string pContentEncoding) 
		{
			MemoryStream ms;
			
			Stream compress = null;


			using (ms = new MemoryStream())
			{
				if (ModuleHandlerHelper.IsEncodingAccepted(app.Context, ModuleHandlerHelper.DEFLATE)) 
				{
					compress = new DeflateStream(ms, CompressionMode.Compress);
					pContentEncoding = ModuleHandlerHelper.DEFLATE;
				}
				else if (ModuleHandlerHelper.IsEncodingAccepted(app.Context, ModuleHandlerHelper.GZIP)) 
				{
					compress = new GZipStream(ms, CompressionMode.Compress);
					pContentEncoding = ModuleHandlerHelper.GZIP;
				} 
			
				if (compress != null)
				{
					compress.Write(pBuffer, 0, pBuffer.Length);
					compress.Dispose();	

					return ms.ToArray ();
				}
				

				return pBuffer;
			}
		}
 
		#endregion
	}
}